﻿namespace dogd
{
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.Diagnostics.CodeAnalysis;
	using System.Linq;
	using System.Reflection;

	/// <summary>
	/// TableMetadata represents all the metadata attributed to a persistable class using
	/// TableAttribute, ColumnAttribute etc.
	/// </summary>
	public class TableMetadata
	{
		/// <summary>
		/// Initialize a new instance.
		/// </summary>
		private TableMetadata()
		{
			this.Columns = new Collection<ColumnMetadata>();
		}

		/// <summary>
		/// Initialize with the persitable type.
		/// </summary>
		/// <param name="metadataType"></param>
		public TableMetadata(Type metadataType)
			: this()
		{
			this.MetadataType = metadataType;
			this.Name = FindTableName(metadataType);

			PropertyInfo[] propertyInfo = metadataType.GetProperties();

			foreach (PropertyInfo p in propertyInfo)
			{
				System.Attribute[] propAttrs = System.Attribute.GetCustomAttributes(p);

				foreach (System.Attribute attr in propAttrs)
				{
					ColumnAttribute column = attr as ColumnAttribute;

					if (column != null)
					{
						this.Columns.Add(new ColumnMetadata(column, p));
					}
				}
			}
		}

		/// <summary>
		/// The type of object to be persisted.
		/// </summary>
		public Type MetadataType
		{
			get;
			private set;
		}

		/// <summary>
		/// The name of the table.
		/// </summary>
		public string Name
		{
			get;
			private set;
		}

		/// <summary>
		/// Collection of column metadata
		/// </summary>
		public Collection<ColumnMetadata> Columns
		{
			get;
			private set;
		}
		
		/// <summary>
		/// Index based on column name
		/// </summary>
		/// <param name="columnName"></param>
		/// <returns></returns>
		public ColumnMetadata this[string columnName]
		{
			get
			{
				var found = new ColumnMetadata();

				foreach (var field in this.Columns)
				{
					if (field.ColumnName == columnName)
					{
						found = field;
						break;
					}
				}

				return found;
			}
		}

		/// <summary>
		/// Collection of column names only
		/// </summary>
		public ReadOnlyCollection<string> ColumnNames
		{
			get
			{
				return this.GetColumnNames(null);
			}
		}

		/// <summary>
		/// Collection of column names that can be inserted to
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Insertable")]
		public ReadOnlyCollection<string> InsertableColumnNames
		{
			get { return this.GetColumnNames(m => !m.IsPrimaryKey); }
		}

		/// <summary>
		/// Collection of all parameters
		/// </summary>
		public ReadOnlyCollection<string> Parameters
		{
			get { return this.GetParameters(null); }
		}

		/// <summary>
		/// Collection of insertable parameters
		/// </summary>
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Insertable")]
		public ReadOnlyCollection<string> InsertableParameters
		{
			get { return this.GetParameters(m => !m.IsPrimaryKey); }
		}

		/// <summary>
		/// Collection of column/variable pairs
		/// </summary>
		public ReadOnlyCollection<string> FormattedParameterStrings
		{
			get
			{
				List<string> parameters = new List<string>();

				foreach (ColumnMetadata field in this.Columns)
				{
					if (!field.IsPrimaryKey)
					{
						parameters.Add(field.FormattedParameterString);
					}
				}

				parameters.Sort();

				return new ReadOnlyCollection<string>(parameters);
			}
		}

		/// <summary>
		/// Metadata associated with the (first) primary key if any.
		/// </summary>
		public ColumnMetadata PrimaryKey
		{
			get
			{
				return this.Columns.Where(f => f.IsPrimaryKey).FirstOrDefault();
			}
		}

		/// <summary>
		/// Metadata associated with the first foreign key if any
		/// </summary>
		public ColumnMetadata ForeignKey
		{
			get
			{
				return this.Columns.Where(f => f.IsForeignKey).FirstOrDefault();
			}
		}

		/// <summary>
		/// Metadata associated with the first lookup field if any
		/// </summary>
		public ColumnMetadata Lookup
		{
			get
			{
				return this.Columns.Where(f => f.IsLookup).FirstOrDefault();
			}
		}

		/// <summary>
		/// Collection of primary key filters
		/// </summary>
		public FilterByCollection PrimaryKeyFilters
		{
			get { return this.GetFilters(m => m.IsPrimaryKey); }
		}

		/// <summary>
		/// Collection of foreign key filters
		/// </summary>
		public FilterByCollection ForeignKeyFilters
		{
			get { return this.GetFilters(m => m.IsForeignKey); }
		}

		/// <summary>
		/// Collection of lookup filters
		/// </summary>
		public FilterByCollection LookupFilters
		{
			get { return this.GetFilters(m => m.IsLookup); }
		}

		/// <summary>
		/// Metadata associated with searchable (text) fields
		/// </summary>
		public Collection<ColumnMetadata> SearchableFields
		{
			get
			{
				var fields = new Collection<ColumnMetadata>();

				foreach (var column in this.Columns)
				{
					if (column.StorageType == System.Data.DbType.String
						&& !column.IsPrimaryKey
						&& !column.IsForeignKey)
					{
						fields.Add(column);
					}
				}

				return fields;
			}
		}

		private static string FindTableName(Type tableType)
		{
			string tableName = string.Empty;

			System.Attribute[] attrs = System.Attribute.GetCustomAttributes(tableType);
			
			foreach (System.Attribute attr in attrs)
			{
				var table = attr as TableAttribute;

				if (table != null)
				{
					tableName = table.TableName;
					break;
				}
			}

			if (string.IsNullOrEmpty(tableName))
			{
				throw new ArgumentException("No TableAttribute found in type", "tableType");
			}

			return tableName;
		}

		private ReadOnlyCollection<string> GetColumnNames(Predicate<ColumnMetadata> match)
		{
			var columnNames = new List<string>();

			foreach (var field in this.Columns)
			{
				if (match == null || match(field))
				{
					columnNames.Add(field.ColumnName);
				}
			}

			return new ReadOnlyCollection<string>(columnNames);
		}

		private ReadOnlyCollection<string> GetParameters(Predicate<ColumnMetadata> match)
		{
			var parameters = new List<string>();

			foreach (var field in this.Columns)
			{
				if (match == null || match(field))
				{
					parameters.Add(field.Parameter);
				}
			}

			return new ReadOnlyCollection<string>(parameters);
		}

		private FilterByCollection GetFilters(Predicate<ColumnMetadata> match)
		{
			var filters = new FilterByCollection();

			foreach (var column in this.Columns)
			{
				if (match == null || match(column))
				{
					filters.Add(new FilterBy(column));
				}
			}

			return filters;
		}
	}
}
